use std::fmt::Display;

use common_base::{
    bigdecimal::BigDecimal,
    chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc},
    prelude::*,
    serde,
    sqlx::{mysql::MySqlValueRef, Decode, MySql, TypeInfo, ValueRef},
};
use common_exception::ErrorCode;

#[derive(Clone, Debug, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)]
#[serde(crate = "self::serde")]
#[serde(rename_all = "camelCase")]
pub enum DataValue {
    Null,
    Bool(bool),
    U8(u8),
    U16(u16),
    U32(u32),
    U64(u64),
    I8(i8),
    I16(i16),
    I32(i32),
    I64(i64),
    F32(f32),
    F64(f64),
    BigDecimal(BigDecimal),
    String(String),
    DateTime(DateTime<Utc>),
    NaiveDate(NaiveDate),
    NaiveTime(NaiveTime),
    NaiveDateTime(NaiveDateTime),
}

impl TryFrom<(usize, MySqlValueRef<'_>)> for DataValue {
    type Error = ErrorCode;

    fn try_from(
        (col_idx, value): (usize, MySqlValueRef<'_>),
    ) -> std::result::Result<Self, Self::Error> {
        let type_name = value.type_info().name().to_owned();

        let error_mapper = |type_name, col_idx, source| {
            ErrorCode::DbFieldNotSupport(format!(
                "can not support field type {} at {}, cause: {}",
                type_name, col_idx, source
            ))
        };

        // see: https://docs.rs/sqlx/latest/sqlx/mysql/types/index.html
        let data_value = match type_name.as_str() {
            "BOOLEAN" => DataValue::Bool(
                <bool as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "TINYINT UNSIGNED" => DataValue::U8(
                <u8 as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "SMALLINT UNSIGNED" => DataValue::U16(
                <u16 as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "INT UNSIGNED" | "MEDIUMINT UNSIGNED" => DataValue::U32(
                <u32 as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "BIGINT UNSIGNED" => DataValue::U64(
                <u64 as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "TINYINT" => DataValue::I8(
                <i8 as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "SMALLINT" => DataValue::I16(
                <i16 as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "INT" | "MEDIUMINT" => DataValue::I32(
                <i32 as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "BIGINT" => DataValue::I64(
                <i64 as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "FLOAT" => DataValue::F32(
                <f32 as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "DOUBLE" => DataValue::F64(
                <f64 as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "NULL" => DataValue::Null,
            "DECIMAL" => DataValue::BigDecimal(
                <BigDecimal as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "CHAR" | "VARCHAR" | "TEXT" => DataValue::String(
                <String as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "DATE" => DataValue::NaiveDate(
                <NaiveDate as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "TIME" => DataValue::NaiveTime(
                <NaiveTime as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "DATETIME" => DataValue::NaiveDateTime(
                <NaiveDateTime as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            "TIMESTAMP" => DataValue::DateTime(
                <DateTime<Utc> as Decode<MySql>>::decode(value)
                    .map_err(|source| error_mapper(type_name, col_idx, source))?,
            ),
            // "YEAR"| "BIT" | "ENUM" | "SET" | "GEOMETRY" | "JSON" | "BINARY" | "ENUM" | "VARBINARY" => {
            //     todo!()
            // }
            // "TINYBLOB" | "TINYTEXT" | "BLOB" | "MEDIUMBLOB" | "MEDIUMTEXT"
            // | "LONGBLOB" | "LONGTEXT" => todo!(),
            _ => {
                return Err(ErrorCode::DbFieldNotSupport(format!(
                    "can not support field type {} at {}",
                    type_name, col_idx
                )))
            }
        };

        Ok(data_value)
    }
}

impl DataValue {
    pub fn parse_bool_from(value: &[u8]) -> Result<bool> {
        if value.len() != 1 {
            Err(ErrorCode::UnImplement(format!(
                "can not parse u8 for bool {:?}",
                value
            )))
        } else {
            match value[0] {
                1 => Ok(true),
                0 => Ok(false),
                _ => Err(ErrorCode::UnImplement(format!(
                    "can not parse u8 for bool {:?}",
                    value
                ))),
            }
        }
    }
}

impl DataValue {
    pub fn as_str(&self) -> Option<&str> {
        if let DataValue::String(inner) = self {
            Some(inner.as_str())
        } else {
            None
        }
    }
}

impl Display for DataValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let string_value = match self {
            DataValue::Null => "".to_owned(),
            DataValue::Bool(inner) => inner.to_string(),
            DataValue::U8(inner) => inner.to_string(),
            DataValue::U16(inner) => inner.to_string(),
            DataValue::U32(inner) => inner.to_string(),
            DataValue::U64(inner) => inner.to_string(),
            DataValue::I8(inner) => inner.to_string(),
            DataValue::I16(inner) => inner.to_string(),
            DataValue::I32(inner) => inner.to_string(),
            DataValue::I64(inner) => inner.to_string(),
            DataValue::F32(inner) => inner.to_string(),
            DataValue::F64(inner) => inner.to_string(),
            DataValue::BigDecimal(inner) => inner.to_string(),
            DataValue::String(inner) => inner.to_string(),
            DataValue::DateTime(inner) => inner.to_string(),
            DataValue::NaiveDate(inner) => inner.to_string(),
            DataValue::NaiveTime(inner) => inner.to_string(),
            DataValue::NaiveDateTime(inner) => inner.to_string(),
        };

        write!(f, "{}", string_value)
    }
}

impl Eq for DataValue {}
impl Ord for DataValue {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Equal)
    }
}

#[derive(Debug, Clone, Copy)]
pub enum ValueType {
    Null,
    Bool,
    U8,
    U16,
    U32,
    U64,
    I8,
    I16,
    I32,
    I64,
    F32,
    F64,
    BigDecimal,
    String,
    DateTime,
    NaiveDate,
    NaiveTime,
    NaiveDateTime,
}
